# gozerbot/generic.py
#
#

""" generic functions """

__copyright__ = 'this file is in the public domain'

from gozerbot.config import config
import time, sys, re, traceback, Queue, urllib, urllib2, socket, random
import os, sgmllib, thread, popen2

exceptionlist = []
hostnamere = re.compile('^[\w\.]+$')

def lockdec(lock):
    """ return locking descriptor locking on the provided lock """
    def locked(func):
        """ lock function func """
        def lockedfunc(*args, **kwargs):
            """ the locked function """
            lock.acquire()
            res = None
            try:
                try:
                    res = func(*args, **kwargs)
                except:
                    handle_exception()
            finally:
                lock.release()
            return res
        return lockedfunc
    return locked

popenwhitelist = re.compile('^[\w\.]+$') # only allow chars, numbers, _ and .
popenlock = thread.allocate_lock()

class PopenWhitelistError(Exception):

    def __init__(self, item):
        self.item = item
        
    def __str__(self):
        return self.item

def gozerpopen(args):
    for i in args:
        if not re.match(popenwhitelist, i):
            raise PopenWhitelistError(i)
    popenlock.acquire()
    try:
        proces = popen2.Popen4(args)
    except:
        popenlock.release()
        raise
    popenlock.release()
    return proces
    
def gozerpopennocheck(args):
    popenlock.acquire()
    try:
        proces = popen2.Popen4(args)
    except:
        popenlock.release()
        raise
    popenlock.release()
    return proces

def die():
    """ stop the bot """
    rlog(10, 'generic', 'sending kill signal')
    os.kill(os.getpid(), 9)

def reboot():
    """ reboot the bot """
    os.execl('./runbot','./runbot')

def calledfrom(frame):
    """ return plugin from which function is called """
    try:
        plugname = frame.f_back.f_code.co_filename
        name = plugname.split('/')[-1][:-3]
    except AttributeError:
        name = None
    del frame
    return name

def stripident(userhost):
    """ strip ident char from userhost """
    if not userhost:
        return None
    doit = config['stripident']
    if not doit:
        return userhost
    if userhost[0] in "~-+^":
        userhost = userhost[1:]
    elif userhost[1] == '=':
        userhost = userhost[2:]
    return userhost

def stripidents(ulist):
    """ strip ident char from list of userhosts """
    result = []
    for userhost in ulist:
        result.append(stripident(userhost))
    return result

def rlog(level, descr, txt):
    """ log text if level >= loglevel """
    if level >= config['loglevel']:
        txt = str(txt)
        sys.stdout.write("[%s] (%s) %s\n" % (uurminsec(time.time()), descr, \
txt))
        sys.stdout.flush()

def handle_exception(ievent=None, log=True):
    """ print exception trace to sys.stderr """
    trace = traceback.extract_tb(sys.exc_traceback)
    result = ""
    exctype, excvalue = sys.exc_info()[:2]
    for i in trace:
        filename = i[0].split('/')[-1]
        linenr = i[1]
        func = i[2]
        result += "%s:%s %s | " % (filename, linenr, func)
    errormsg = "EXCEPTION: %s%s: %s" % (result, exctype, excvalue)
    exceptionlist.append(errormsg)
    if ievent:
        ievent.reply(errormsg)
    if log:
        rlog(100, "ALERT", "EXCEPTION !!")
        traceback.print_exc(sys.stderr)

def getlistensocket(listenip):
    """ scan for a free socket on listenip """
    port = 5000
    while 1:
        time.sleep(0.01)
        try:
            port += 1
            if ':' in listenip:
                sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
            else:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            sock.setblocking(1)
            if port > 65000:
                port = 5000
            sock.bind((listenip, port))
            sock.listen(1)
            return (port, sock)
        except Exception, ex:
            pass

leapfactor = float(6*60*60)/float(365*24*60*60)

def elapsedstring(nsec, ywd = None):
    """ return string of elapsed time """
    nsec = int(float(nsec))
    year = 365*24*60*60
    week = 7*24*60*60
    day = 24*60*60
    hour = 60*60
    minute = 60
    nsec -= nsec * leapfactor
    years = int(nsec/year)
    nsec -= years*year
    weeks = int(nsec/week)
    nsec -= weeks*week
    days = int(nsec/day)
    nsec -= days*day
    hours = int(nsec/hour)
    nsec -= hours*hour
    minutes = int(nsec/minute)
    sec = int(nsec - minutes*minute)   
    result = ''
    if (years > 1):
        result = str(years) + " years "
    if (years == 1):
        result = "1 year "
    if (weeks > 1):
        result += str(weeks) + " weeks "
    if (weeks == 1):
        result += "1 week "
    if (days > 1):
        if ywd:
            result += 'and '+ str(days) + " days"
        else:
            result += str(days) + " days "
    if (days == 1):
        if ywd:
            result += 'and 1 day'
        else:
            result += "1 day "
    if ywd:
        return result
    if (hours > 1):
        result += str(hours) + " hours "
    if (hours == 1):
        result += "1 hour "
    if (minutes > 1):
        result += str(minutes) + " minutes "
    if (min == 1):
        result += "1 minute "
    if sec == 0:
        if result:
            return result
        else:
            return 0
    if (sec == 1):
        if result:
            result += "and 1 second "
        else:
            result = "1 second"
    else:
        if result:
            result += "and " + str(sec) + " seconds"
        else: 
            result = str(sec) + " seconds"
    return result.strip()

def hourmin(ttime):
    """ extract hour:minutes from time """
    result = ""
    timeres = time.localtime(ttime)
    if timeres[3] < 10:
        result += "0" + str(timeres[3]) + ":"
    else:
        result += str(timeres[3]) + ":"
    if timeres[4] < 10:
        result += "0" + str(timeres[4])
    else:
        result += str(timeres[4])
    return result

timere = re.compile('(\S+)\s+(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+(\d+)')

bdmonths = ['Bo', 'Jan', 'Feb', 'Mar', ' Apr', 'May', 'Jun', 'Jul', 'Aug', \
'Sep', 'Oct', 'Nov', 'Dec']

def striptime(what):
    """ strip time indicators from string """
    what = str(what)
    what = re.sub('\S+-\S+-\S+', '', what)
    what = re.sub('\S+-\S+', '', what)
    what = re.sub('\S+:\S+', '', what)
    what = re.sub('\s+', ' ', what)
    return what

def today():
    """ get time for today 00:00 """
    if time.daylight:
        ttime = time.ctime(time.time() + int(time.timezone) + 3600)
    else:
        ttime = time.ctime(time.time() + int(time.timezone))
    matched = re.search(timere, ttime)
    if matched:
        temp = "%s %s %s" % (matched.group(3), matched.group(2), \
matched.group(7))
        timestring = time.strptime(temp, "%d %b %Y")
        result = time.mktime(timestring)
        return result

def strtotime(what):
    """ convert string to time """
    daymonthyear = 0
    hoursmin = 0
    try:
        # check for day-month-year
        dmyre = re.search('(\S+)-(\S+)-(\S+)', str(what))
        if dmyre:
            (day, month, year) = dmyre.groups()
            date = "%s %s %s" % (day, bdmonths[int(month)], year)
            daymonthyear = time.mktime(time.strptime(date, "%d %b %Y"))
        else:
            # check for day-month
            dmre = re.search('(\S+)-(\S+)', str(what))
            if dmre:
                (day, month) = dmre.groups()
                date = "%s %s %s" % (day, bdmonths[int(month)], \
time.localtime()[0])
                daymonthyear = time.mktime(time.strptime(date,"%d %b %Y"))
        # check for hours:minutes:seconds
        hmsre = re.search('(\S+):(\S+):(\S+)', str(what))
        if hmsre:
            hours = 60 * 60 * (int(hmsre.group(1)))
            hoursmin = hours  + int(hmsre.group(2)) * 60
            hms = hoursmin + int(hmsre.group(3))
        else:
            # check for hours:minutes
            hmre = re.search('(\S+):(\S+)', str(what))
            if hmre:
                hours = 60 * 60 * (int(hmre.group(1)))
                hms = hours  + int(hmre.group(2)) * 60
            else:
                hms = 0
        if not daymonthyear and not hms:
            return None
        if daymonthyear == 0:
            heute = today()
        else:
            heute = daymonthyear
        return heute + hms
    except ValueError:
        return None
    except Exception, ex:
        handle_exception()
        return None

def uurminsec(ttime):
    """ return hours:minutes:seconds string """
    result = ""
    timeres = time.localtime(ttime)
    if timeres[3] < 10:
        result += "0" + str(timeres[3]) + ":"
    else:
        result += str(timeres[3]) + ":"
    if timeres[4] < 10:
        result += "0" + str(timeres[4]) + ":"
    else:
        result += str(timeres[4]) + ":"
    if timeres[5] < 10:
        result += "0" + str(timeres[5])
    else:
        result += str(timeres[5])
    return result


def getdaymonth(ttime):
    """ return day-month tuple"""
    timestr = time.ctime(ttime)
    result = re.search(timere, timestr)
    if result:
        return (result.group(3), result.group(2))
    else:
        return (None, None)

def getdaymonthyear(ttime):
    """ return day-month-year tuple"""
    timestr = time.ctime(ttime)
    result = re.search(timere, timestr)
    if result:
        return (result.group(3), result.group(2), result.group[7])
    else:
        return (None, None, None)

def dmy(ttime):
    """ return day-month-year string"""
    timestr = time.ctime(ttime)
    result = re.search(timere, timestr)
    if result:
        return "%s %s %s" % (result.group(3), result.group(2), result.group(7))
    else:
        return None

def checkchan(item):
    """ check if chan <channel> is in string """ 
    chanre = re.search(' chan (\S+)', item)
    if chanre:
        chan = str(chanre.group(1))
        item = re.sub(' chan ' + chan, '', item)
        return (chan.lower(), item)

def getwho(bot, who):
    """ get userhost from bots userhost cache """
    who = who.lower()
    try:
        result = bot.userhosts[who]
        return result
    except KeyError:
        return None

def waitforuser(bot, nick):
    """ wait for user response """
    queue = Queue.Queue()
    waitnr = bot.privwait.register(nick, queue)
    result = queue.get()
    bot.privwait.delete(waitnr)
    return result

def waitforuserdcc(bot, nick):
    """ wait for user dcc response """
    queue = Queue.Queue()
    waitnr = bot.wait.register('DCC', nick, queue)
    result = queue.get()
    bot.wait.delete(waitnr)
    return result

class CBURLopener(urllib.FancyURLopener):

    """ our URLOpener """

    def __init__(self, *args):
        self.version = "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2.1) \
Gecko/20030225"
        #self.version += " GOZERBOT see http://r8.cg.nu"
        urllib.FancyURLopener.__init__(self, *args)

def geturl(url):
    """ fetch an url """
    urllib._urlopener = CBURLopener()
    rlog(5, 'generic', 'fetching %s' % url)
    result = urllib.urlopen(url)
    tmp = result.read()
    result.close()
    return tmp

def geturl2(url):
    """ use urllib2 to fetch an url """
    rlog(10, 'generic', 'fetching %s' % url)
    result = urllib2.urlopen(url)
    tmp = result.read()
    result.close()
    return tmp

def getrandomnick():
    """ generate random nick """
    return "gb" + str(random.randint(0, 100))

class Stripper(sgmllib.SGMLParser):

    """ html stripper """

    def __init__(self):
        sgmllib.SGMLParser.__init__(self)

    def strip(self, some_html):
        """ strip html """
        self.theString = ""
        self.feed(some_html)
        self.close()
        return self.theString

    def handle_data(self, data):
        """ data handler """
        self.theString += data

def striphtml(txt):
    """ strip html from txt """
    stripper = Stripper()
    return stripper.strip(txt)

def waitforqueue(queue):
    """ wait for queue output """
    result = []
    while 1:
        try:
            res = queue.get(1, 15)
        except Queue.Empty:
            break
        if not res:
            break
        result.append(res)
    return result
